package org.apache.ibatis.common.io;

import org.apache.ibatis.common.logging.Log;
import org.apache.ibatis.common.logging.LogFactory;

import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * @Name DefaultVFS
 * @Description: 默认 VFS [实现] 类
 * 描述信息：默认的 VFS 实现类，其 isValid 函数恒返回 true。因此，它作为 VFS 的缺省实现。
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-05-14 17:08:44
 **/
public class DefaultVFS extends VFS {
    private static final Log log = LogFactory.getLog(DefaultVFS.class);
    // 这个逻辑头指明是一个 JAR（ZIP）文件。
    private static final byte[] JAR_MAGIC = {'P', 'K', 3, 4};

    @Override
    public boolean isValid() {
        return true;
    }

    @Override
    public List<String> list(URL url, String path) throws IOException {
        InputStream in = null;
        try {
            List<String> resources = new ArrayList<>();
            URL jarUrl = findJarForResource(url);
            if (jarUrl != null) {
                in = jarUrl.openStream();
                if (log.isDebugEnabled()) log.debug("清单：" + url);
                resources = listResources(new JarInputStream(in), path);
            } else {
                List<String> children = new ArrayList<>();
                try {
                    if (isJar(url)) {
                        in = url.openStream();
                        try (JarInputStream jarInputStream = new JarInputStream(in)) {
                            if (log.isDebugEnabled()) log.debug("清单：" + url);
                            for (JarEntry entry; (entry = jarInputStream.getNextJarEntry()) != null; ) {
                                if (log.isDebugEnabled()) log.debug("Jar 实体：" + entry.getName());
                                children.add(entry.getName());
                            }
                        }
                    } else {
                        in = url.openStream();
                        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                        List<String> lines = new ArrayList<>();
                        for (String line; (line = reader.readLine()) != null; ) {
                            if (log.isDebugEnabled()) log.debug("Reader 实体：" + line);
                            lines.add(line);
                            if (getResources(path + "/" + line).isEmpty()) {
                                lines.clear();
                                break;
                            }
                        }
                        if (!lines.isEmpty()) {
                            if (log.isDebugEnabled()) log.debug("清单：" + url);
                            children.addAll(lines);
                        }
                    }
                } catch (FileNotFoundException e) {
                    if ("file".equals(url.getProtocol())) {
                        File file = new File(url.getFile());
                        if (log.isDebugEnabled()) log.debug("清单目录：" + file.getAbsolutePath());
                        if (file.isDirectory()) {
                            if (log.isDebugEnabled()) log.debug("清单：" + url);
                            children = Arrays.asList(file.list());
                        }
                    } else throw e;
                }
                String prefix = url.toExternalForm();
                if (!prefix.endsWith("/")) prefix += "/";
                for (String child : children) {
                    String resourcePath = path + "/" + child;
                    resources.add(resourcePath);
                    URL childUrl = new URL(prefix + child);
                    resources.addAll(list(childUrl, resourcePath));
                }
            }
            return resources;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    // nothing to do.
                }
            }
        }
    }

    protected URL findJarForResource(URL url) throws MalformedURLException {
        if (log.isDebugEnabled()) log.debug("发现 JAR URL：" + url);
        try {
            for (; ; ) {
                url = new URL(url.getFile());
                if (log.isDebugEnabled()) log.debug("内嵌 URL：" + url);
            }
        } catch (MalformedURLException e) {
            // 这将在某个时刻发生，并作为循环中的一个中断。
        }
        StringBuffer jarUrl = new StringBuffer(url.toExternalForm());
        int index = jarUrl.lastIndexOf(".jar");
        if (index >= 0) {
            jarUrl.setLength(index + 4);
            if (log.isDebugEnabled()) log.debug("待扩展后缀的 JAR URL：" + jarUrl);
        } else {
            if (log.isDebugEnabled()) log.debug("不是一个 JAR -> " + jarUrl);
            return null;
        }
        try {
            URL testUrl = new URL(jarUrl.toString());
            if (isJar(testUrl)) return testUrl;
            else {
                if (log.isDebugEnabled()) log.debug("不是一个 JAR -> " + jarUrl);
                jarUrl.replace(0, jarUrl.length(), testUrl.getFile());
                File file = new File(jarUrl.toString());
                if (!file.exists()) {
                    try {
                        file = new File(URLEncoder.encode(jarUrl.toString(), "UTF-8"));
                    } catch (UnsupportedEncodingException e) {
                        String msg = "不支持 UTF-8 编码？那是不可能的吧？";
                        throw new RuntimeException(msg);
                    }
                }
                if (file.exists()) {
                    if (log.isDebugEnabled()) log.debug("尝试真实的文件：" + file.getAbsolutePath());
                    testUrl = file.toURI().toURL();
                    if (isJar(testUrl)) return testUrl;
                }
            }
        } catch (MalformedURLException e) {
            log.warn("无效的 JAR URL：" + jarUrl);
        }
        if (log.isDebugEnabled()) log.debug("不是一个 JAR -> " + jarUrl);
        return null;
    }

    protected List<String> listResources(JarInputStream jar, String path) throws IOException {
        if (!path.startsWith("/")) path += "/";
        if (!path.endsWith("/")) path += "/";
        List<String> resources = new ArrayList<>();
        for (JarEntry entry; (entry = jar.getNextJarEntry()) != null; ) {
            if (!entry.isDirectory()) {
                StringBuffer name = new StringBuffer(entry.getName());
                if (name.charAt(0) != '/') name.insert(0, '/');
                if (name.indexOf(path) == 0) {
                    if (log.isDebugEnabled()) log.debug("发现资源：" + name);
                    resources.add(name.substring(1));
                }
            }
        }
        return resources;
    }

    protected String getResourcePath(String packageName) {
        return packageName == null ? null : packageName.replace('.', '/');
    }

    protected boolean isJar(URL url) {
        return isJar(url, new byte[JAR_MAGIC.length]);
    }

    protected boolean isJar(URL url, byte[] buffer) {
        InputStream in = null;
        try {
            in = url.openStream();
            in.read(buffer, 0, JAR_MAGIC.length);
            if (Arrays.equals(buffer, JAR_MAGIC)) {
                if (log.isDebugEnabled()) log.debug("发现 JAR：" + url);
                return true;
            }
        } catch (Exception e) {
            // this is not a jar
        } finally {
            if (in != null) try {
                in.close();
            } catch (Exception e) {
                // nothing to do.
            }
        }
        return false;
    }
}

